﻿using DimensionsHelper.LanguageService.Symbols;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

public partial class Preprocessor
{
    /// <summary>
    ///     读取节点直到<paramref name="until" />列表中的节点类型，
    ///     同时报告Tag是Unnecessary的诊断信息，文本区间是路过的区间。
    /// </summary>
    /// <param name="target">添加到的节点列表</param>
    /// <param name="until">截止的节点类型</param>
    /// <returns>截止时的文本指针位置</returns>
    private EndPosition ProcessBlockUnused(PreNodeList target, params SyntaxKinds[] until)
    {
        int end = _currentNode.End;
        int endLine = _currentNode.EndLine;

        while (!_lexer.Eof())
        {
            SourceNode token = ProcessNode();
            end = token.End;
            endLine = token.EndLine;

            if (Array.IndexOf(until, token.Kind) != -1)
            {
                break;
            }

            target.AddNode(token);
        }

        UnUse(target.Span);
        return new EndPosition(end, endLine);
    }


    /// <summary>
    ///     将后续节点添加到目标列表，依据<paramref name="condition" />的值，
    ///     决定是否报告未使用诊断信息。
    /// </summary>
    /// <param name="target">目标节点列表</param>
    /// <param name="condition">如果此值是<see langword="false" />，报告未使用的诊断信息</param>
    /// <param name="endKinds">区块结束节点类型</param>
    /// <returns>扫描节点停止时的文本位置</returns>
    private EndPosition ProcessConditionBlock(PreNodeList target, bool condition,
        params SyntaxKinds[] endKinds)
    {
        EndPosition end;

        if (condition)
        {
            PreNodeList? backupBlock = _currentBlock;
            _currentBlock = target;

            while (Process(endKinds))
            {
            }

            end = new EndPosition(_currentNode.End, _currentNode.EndLine);
            _currentBlock = backupBlock;
        }
        else
        {
            end = ProcessBlockUnused(target, endKinds);
        }

        return end;
    }


    /// <summary>
    ///     处理 #if 语句。
    /// </summary>
    /// <param name="ifKeywordToken">#if 关键字</param>
    /// <param name="preBlock">
    ///     此#if语句分支的前一个分支，如果是第一个，传入<see langword="null" />
    /// </param>
    private void ProcessIfStatement(SourceNode ifKeywordToken, PreIfStatement? preBlock)
    {
        PreExpression? condition = null;
        // 不是 #else
        if (!ifKeywordToken.IsNode(SyntaxKinds.PreElseKeywordToken))
        {
            if (!IsNextWrap())
            {
                condition = ProcessExpression();
            }
            else
            {
                Raise(DiagnosticDescriptors.UnexpectedToken, ifKeywordToken.End, 1, "表达式");
            }
        }

        // 需要换行
        ExpectLinebreakAndSkip();

        PreIfStatement stat = NodeFactory.CreatePreIfStatement(null, ifKeywordToken, condition, null);
        EndPosition end;

        if (preBlock is { Succeed: true } ||
            (condition != null && !ComputeExpressionAsBoolean(condition)))
        {
            end = ProcessBlockUnused(stat,
                SyntaxKinds.PreElseKeywordToken,
                SyntaxKinds.PreElIfKeywordToken,
                SyntaxKinds.PreEndIfKeywordToken,
                SyntaxKinds.Eof);
        }
        else
        {
            stat.Succeed = condition == null // 当表达式错误时，视为 true
                           || ComputeExpressionAsBoolean(condition);
            PreNodeList? currentBlock = _currentBlock;
            _currentBlock = stat;
            // 读取有效的子节点
            while (Process(
                       SyntaxKinds.PreElIfKeywordToken,
                       SyntaxKinds.PreElseKeywordToken,
                       SyntaxKinds.PreEndIfKeywordToken,
                       SyntaxKinds.Eof))
            {
            }

            end = new EndPosition(_currentNode.End, _currentNode.EndLine);
            _currentBlock = currentBlock;
        }

        FinishToken(stat, ifKeywordToken.Start, end.Position - ifKeywordToken.Start);
        stat.UpdateLine(ifKeywordToken.StartLine, end.Line);

        if (preBlock != null)
        {
            preBlock.Update(stat);
            FinishToken(preBlock, preBlock.Start, _currentNode.End - preBlock.Start);
        }

        // #elif / #else
        if (_currentNode.IsOneOfNode(SyntaxKinds.PreElIfKeywordToken,
                SyntaxKinds.PreElseKeywordToken))
        {
            ProcessIfStatement(_currentNode, stat);
        }

        // 如果是第一个分支，将 #if 语句块添加到节点列表中。
        if (preBlock == null)
        {
            (_currentBlock ?? _processedFile).AddNode(stat);
        }
    }


    /// <summary>
    ///     处理 #ifdef 语句。
    /// </summary>
    /// <param name="ifDefOrNDefKeywordToken">#ifdef 或 #ifndef 关键字</param>
    private void ProcessIfDefOrNDefStatement(SourceNode ifDefOrNDefKeywordToken)
    {
        int start = ifDefOrNDefKeywordToken.Start;
        SourceIdentifier? macroName = null;
        if (NextExpect(SyntaxKinds.Identifier, false, out SourceNode? nameNode))
        {
            macroName = NodeFactory.CreateSourceIdentifier(nameNode);
        }

        // 需要换行
        ExpectLinebreakAndSkip();
        PreIfStatement stat = NodeFactory.CreatePreIfDefOrNDefStatement(null,
            ifDefOrNDefKeywordToken, macroName, null);

        // 依据传入的 ifdefOrndefKeywordToken 的关键字类型，判断是否读取后续内容
        // #ifdef  - 后续宏名已定义时成功
        // #ifndef - 后续宏名未定义时成功
        bool succeed = false;
        if (!SourceNode.IsNullOrDefault(macroName))
        {
            if (ifDefOrNDefKeywordToken.IsNode(SyntaxKinds.PreIfDefKeywordToken) &&
                _macroSymbolTable.TryGetSymbol(macroName.Text, out ISymbol? sym))
            {
                sym.Reference(macroName.GetLocation());
                succeed = true;
            }

            if (ifDefOrNDefKeywordToken.IsNode(SyntaxKinds.PreIfNDefKeywordToken) &&
                !_macroSymbolTable.ContainsName(macroName.Text))
            {
                succeed = true;
            }
        }

        // 和 #if 不同的是，#ifdef 和 #ifndef 不可以衔接 #elif 或 #else
        EndPosition end = ProcessConditionBlock(stat, succeed,
            SyntaxKinds.PreEndIfKeywordToken, SyntaxKinds.Eof);
        FinishToken(stat, start, end.Position - start);
        stat.UpdateLine(ifDefOrNDefKeywordToken.Start, end.Line);
        (_currentBlock ?? _processedFile).AddNode(stat);
    }

    private readonly struct EndPosition(int pos, int line)
    {
        public int Position { get; } = pos;

        public int Line { get; } = line;
    }
}